package game.Gamemode;

import com.sun.deploy.security.SelectableSecurityManager;
import game.Map.Tile;
import game.Player.Damageable;
import game.Player.Player;
import game.Map.Tile;
import game.Scene;
import game.SceneObject;
import game.Vector3;
import game.Weapons.Bullet;
import game.Weapons.LoadWeapons;
import game.Weapons.Weapon_info;
import javafx.scene.paint.Color;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public abstract class GamemodeManager extends SceneObject {

    protected static GamemodeManager instance;
    protected List<Player> playerList;
    protected List<ChatMessage> chatMessages;
    protected List<KillLog> killLogs;

    public GamemodeManager()  {
        if(GamemodeManager.instance != null){
            try {
                throw new Exception("GamemodeManager instance not null. Delete first scene before creating another one.");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        GamemodeManager.instance = this;

        this.playerList = new ArrayList<Player>();
        this.chatMessages = new ArrayList<ChatMessage>();
        this.killLogs = new ArrayList<KillLog>();
    }

    public static GamemodeManager getInstance(){
        return GamemodeManager.instance;
    }

    @Override
    public void destroy() {
        GamemodeManager.instance = null;
        super.destroy();
    }
    // GAMEMODE RELATED


    // CHAT RELATED
    /**
     * Adds a message to the chat.
     * @param msg Message to be added.
     */
    public void addChatMessage(ChatMessage msg){
        this.chatMessages.add(msg);
    }
    /**
     * Returns the list of all messages in the chatbox.
     * @return The list of messages in the chatbox.
     */
    public List<ChatMessage> getChatMessages(){
        return this.chatMessages;
    }

    /**
     * Returns the list of messages in the chatbox seen by a player.
     * @return The list of messages in the chatbox seen by player pl.
     */
    public List<ChatMessage> getChatMessages(Player pl){

        List<ChatMessage> res = new ArrayList<ChatMessage>();

        for(int i = 0; i < chatMessages.size(); i++){
            ChatMessage msg = chatMessages.get(i);
            if(msg.isGlobal()|| msg.getSenderColor() == pl.getTeam().getTeamColor()){
                res.add(msg);
            }
        }

        return res;
    }

    /**
     * Returns the list of messages in the chatbox sent after a given timestamp in milliseconds.
     * @return The list of messages in the chatbox sent after a given timestamp in milliseconds.
     */
    public List<ChatMessage> getChatMessagesAfter(long time){
        List<ChatMessage> res = new ArrayList<ChatMessage>();

        for(int i = 0; i < chatMessages.size(); i++){
            ChatMessage msg = chatMessages.get(i);

            if(msg.sendTime > time){
                res.add(msg);
            }
        }

        return res;
    }

    /**
     * Returns the list of messages in the chatbox sent after a given timestamp in milliseconds viewed by a player.
     * @return The list of messages in the chatbox sent after a given timestamp in milliseconds seen by a player.
     */
    public List<ChatMessage> getChatMessagesAfter(long time, Player pl){
        List<ChatMessage> res = new ArrayList<ChatMessage>();

        for(int i = 0; i < chatMessages.size(); i++){
            ChatMessage msg = chatMessages.get(i);

            if(msg.sendTime > time && (msg.isGlobal()|| msg.getSenderColor() == pl.getTeam().getTeamColor())){
                res.add(msg);
            }
        }

        return res;
    }


    // KILL LOGS
    /**
     * Adds a KillLog to the logs list.
     * @param log KillLog to be added.
     */
    public void addKillLog(KillLog log){
        this.killLogs.add(log);
    }

    /**
     * Returns the list of all killlogs in the logs.
     * @return The list of killogs in the logs.
     */
    public List<KillLog> getKillLogs(){
        return this.killLogs;
    }

    /**
     * Returns the list of killogs in the logs sent after a given timestamp in milliseconds.
     * @return The list of killogs in the logs sent after a given timestamp in milliseconds.
     */
    public List<KillLog> getKillLogsAfter(long time){
        List<KillLog> res = new ArrayList<KillLog>();

        for(int i = 0; i < killLogs.size(); i++){
            KillLog log = killLogs.get(i);

            if(log.getKilledTime() > time){
                res.add(log);
            }
        }

        return res;
    }


    /**
     * Called when the game is started.
     */
    protected void gameStarted() {

    }

    /**
     * Called when the game is finished.
     */
    protected void gameFinished() {

    }

    /**
     * Called on every frame
     *
     * @param deltaTime Time passed since last frame was rendered.
     */
    public void update(double deltaTime) {

    }

    /**
     * Called when a player spawns in a game.
     *
     * @param player The player that spawns.
     */
    public abstract void playerSpawned(Player player);

    // DAMAGE RELATED FUNCTIONS

    /**
     * Called when a bullet is shot by a player during a game
     * @param bullet The bullet that was shot
     */
    //public abstract void bulletShot(Bullet bullet);

    /**
     * Called when a bullet hits a damageable object.
     * Must inflict damage to the object if needed and destroy the bullet.
     *
     * @param bullet The bullet that hit the object
     * @param obj    The object that was hit
     */
    public abstract boolean bulletHit(Bullet bullet, Damageable obj);

    /**
     * Called when a player knifes a damageable object.
     * Must inflict damage to the object if needed and destroy the bullet.
     *
     * @param killer The player that hit the object
     * @param knife The knife weapon used
     * @param obj    The object that was hit
     */
    public abstract boolean knifeHit(Player killer, Weapon_info knife, Damageable obj);

    /**
     * Called when a player kills another player.
     * Must add score or remove score from players if needed.
     *
     * @param victim     The player that was killed.
     * @param killer     The killer player.
     * @param weaponInfo The weapon that was used to kill the player.
     */

    public void playerKilled(Player victim, Player killer, Weapon_info weaponInfo){
        if(weaponInfo != null){
            KillLog log = new KillLog(killer.getName(), killer.getTeam().getTeamColor(),
                    weaponInfo.getName(), weaponInfo.getShopImage(),
                    victim.getName(), victim.getTeam().getTeamColor());
            this.addKillLog(log);
        }else{
            KillLog log = new KillLog(killer.getName(), killer.getTeam().getTeamColor(),
                    "", null,
                    victim.getName(), victim.getTeam().getTeamColor());
            this.addKillLog(log);
        }
    }

    // MOVEMENT RELATED FUNCTIONS

    /**
     * Called when a player moves to a new tile.
     *
     * @param player  The player that moved.
     * @param newTile The tile that the player moved to.
     * @param oldTile The tile that the player moved from.
     */
    public abstract void playerChangedTile(Player player, Tile newTile, Tile oldTile);

    // SHOP RELATED FUNCTIONS

    /**
     * Called when a player buys a weapon through the shop during game.
     *
     * @param player     The player that bought the weapon.
     * @param weaponInfo The weapon that the player bought.
     */
    //public abstract void playerBoughtWeapon(Player player, Weapon_info weaponInfo);

    /**
     *
     * @return
     */


    public List<Player> getPlayerList() {
        return playerList;
    }


    public void addPlayer(Player p) {
        this.playerList.add(p);
    }

    /**
     * Returns remaining time of the current round
     */
    public abstract double getRemainingTime();

    public abstract LoadWeapons getWeapons();

    public abstract double getShopCloseTime();

    public abstract boolean isGameFinished();
    public abstract String getGameFinishedText();
    public abstract Color getGameFinishedColor();

    public abstract HashMap<Weapon_info, Vector3> getDroppedWeapons();

}